package com.quesucede.gameoflife;

import java.util.HashSet;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.WindowManager;

/**
 * GridView manages the animation and look of the grid. It contains
 * Paint objects to color code the cells, as well as an inner static
 * Handler class that updates the board accordingly
 * @author Brett Kromkamp, William Sugarman, and Brian Pokrifka
 *
 */
public class GridView extends View {
	
	public static final int PAUSE = 0;
	public static final int RUNNING = 1;
	
	/**
	 * The mode the application is currently running in
	 */
	private int mode;
	
	/**
	 * A reference to the underlying grid implementation
	 */
	private Life _life;
	
	/**
	 * The delay inbetween updates
	 */
	private long _moveDelay = 250;
	
	/**
	 * A reference to the static handler that updates the grid
	 */
	private RefreshHandler _redrawHandler;
	
	/**
	 * The background color
	 */
	private Paint backgroundPaint = new Paint();
	
	/**
	 * The paint color for new cells as indicated in the XML as cell0
	 */
	private Paint newCellPaint = new Paint();
	
	/**
	 * The paint color for younger cells as indicated in the XML as cell1
	 */
	private Paint youngCellPaint = new Paint();
	
	/**
	 * The paint color for moderately aged cells as indicated
	 * in the XML as cell2
	 */
	private Paint middleAgedCellPaint = new Paint();
	
	/**
	 * The paint color for older cells as indicated in the XML as cell3
	 */
	private Paint olderCellPaint = new Paint();
	
	/**
	 * The paint color for really old cells as indicated in the XML as cell4
	 */
	private Paint ancientCellPaint = new Paint();
	
	/**
	 * the most recently changed square.  We don't want to toggle the same one we just changed
	 */
	private Pair<Integer, Integer> mostRecentlyChangedPoint = new Pair<Integer, Integer>(-1, -1);
	
	/**
	 * A static handler class that updates the grid based on
	 * the underlying Life implementation every so often as
	 * indicated in the settings
	 * @author Brett Kromkamp, William Sugarman, and Brian Pokrifka 
	 *
	 */
    static class RefreshHandler extends Handler {
    	
    	/**
    	 * A reference to the view
    	 */
    	private GridView viewRef;
    	
    	/**
    	 * Updates the viewRef to the viewInstance
    	 * @param viewInstance - an instance of the GridView to modify
    	 */
    	public RefreshHandler(GridView viewInstance)
    	{
    		viewRef = viewInstance;
    	}

        @Override
        /**
         * Updates the game grid and prompts it for redraw
         */
        public void handleMessage(Message message) {
        	if( viewRef.mode == GridView.RUNNING)
        	{
	            viewRef.update();
	            viewRef.invalidate();
        	}
        }

        /**
         * Sleeps the thread for some amount of milliseconds and
         * removes any pending messages
         * @param delayMillis - the time to sleep
         */
        public void sleep(long delayMillis) {
        	removeMessages(0);
            sendMessageDelayed(obtainMessage(0), delayMillis);
        }
    };

    /**
     * Consumes a given context and attribute set to initialize
     * the view for the Game of Life Grid. The constructor is
     * responsible for making sure the board is adjusted based on
     * the physical size of the device. It also initializes all of
     * the painters to be used.
     * @param context - the given context
     * @param attrs - XML attributes
     */
	public GridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        // Fit the board to the physical screen
        DisplayMetrics metrics = new DisplayMetrics();
        ((WindowManager) (this.getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay().getMetrics(metrics);
        _life = new Life(context, metrics.widthPixels, metrics.heightPixels);
        
        // 5 different levels of time alive, gradually changing cells from white to red
        backgroundPaint.setColor(getResources().getColor(R.color.background));
  		newCellPaint.setColor(getResources().getColor(R.color.cell0));
  		youngCellPaint.setColor(getResources().getColor(R.color.cell1));
  		middleAgedCellPaint.setColor(getResources().getColor(R.color.cell2));
  		olderCellPaint.setColor(getResources().getColor(R.color.cell3));
  		ancientCellPaint.setColor(getResources().getColor(R.color.cell4));
  		
  		this.mode = GridView.RUNNING;
  		_redrawHandler = new RefreshHandler(this);
  		initGridView();
  		this.update();
	}
	
	/**
	 * Iterates through a set of distinct points on the grid and
	 * toggles their status between life and death before redrawing
	 * @param points - a HashSet of grid (x,y) pairs
	 */
	public void toggle(HashSet< Pair<Integer, Integer> > points) {
		Log.i("motion event", "toggling "+points.size()+" squares.");
		for(Pair<Integer, Integer> p : points) {
			if (p.first != mostRecentlyChangedPoint.first || p.second != mostRecentlyChangedPoint.second) {
				_life.toggleLife(p.first, p.second);
				mostRecentlyChangedPoint = p;
			}
		}
		this.invalidate();

	}
	
	/**
	 * Returns the current mode
	 * @return the mode
	 */
	public int getMode() {
		return this.mode;
	}
	
	/**
	 * Toggles between pausing and running the application
	 */
	public void switchMode() {
		if (this.mode == PAUSE) {
			this.mode = RUNNING;
			this.update();
		}
		else {
			this.mode = PAUSE;
		}
	}
	
	/**
	 * A method implicitly called through invalidate(). onDraw will
	 * iterate through the grid and update the coloring of each cell
	 * based on their age
	 * @param canvas - the canvas upon which to draw
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		boolean coloring = SettingsFragment.getColorCoding(getContext());
		// Draw the background
		canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);

		// Draw the cells
		for (int h = 0; h < _life.height; h++) {
			for (int w = 0; w < _life.width; w++) {
				if (_life.getGrid()[h][w] != 0) {
					Paint cellColor;
					if (coloring){
						
						/* Cells that just turned on are case 0,
						 * Cells that have been alive for 1 turn are case 1,
						 * Cells that have been alive between 2 and 4 turns are case 2,
						 * Cells that have been alive between 5 and 7 turns are case 3,
						 * Cells that have been alive longer are the default case
						 * */
						switch (_life.getTimeAliveGrid()[h][w]){
							case 0:
								cellColor = newCellPaint;
								break;
							case 1:
								cellColor = youngCellPaint;
								break;
							case 2:
							case 3:
							case 4:
								cellColor = middleAgedCellPaint;
								break;
							case 5:
							case 6:
							case 7:
								cellColor = olderCellPaint;
								break;
							default:
								cellColor = ancientCellPaint;
								break;
							}
					}
					else {
						cellColor = newCellPaint;
					}
					canvas.drawRect(
						w * Life.CELL_SIZE, 
						h * Life.CELL_SIZE, 
						(w * Life.CELL_SIZE) + (Life.CELL_SIZE -1),
						(h * Life.CELL_SIZE) + (Life.CELL_SIZE -1),
						cellColor);
				}
			}
		}
	}
	
	/**
	 * Generates the next generation of cells before sleeping the handler.
	 * Note that this method is always called by the handler itself
	 */
	private void update() {
		_life.generateNextGeneration();
		
		//Very fast = 4 times normal speed
		//Very slow = .4 times normal speed
		String speed = SettingsFragment.getAnimationSpeed(getContext());
		if (speed.equals("5")) _moveDelay = 1000;
		else if (speed.equals("4")) _moveDelay = 500;
		else if (speed.equals("2")) _moveDelay = 150;
		else if (speed.equals("1")) _moveDelay = 100;
		else _moveDelay = 250;
		_redrawHandler.sleep(_moveDelay);
	}
	
	/**
	 * Brings the grid into focus
	 */
	private void initGridView() {
		setFocusable(true);
	}
}